#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024/10/21 9:20
# @Author  : zy
# @File    : word_tpl.py
"""
pip install docxtpl

"""
import csv
import json
import uuid
from pathlib import Path

import pandas as pd
from docxtpl import DocxTemplate
from datetime import datetime, date
from dateutil import relativedelta
import random


# 日期对象转为日期时间对象
def _date_to_datetime(dt: date):
    return datetime.combine(dt, datetime.min.time())


# 字典追加进csv 文件
def _dict_to_csv(filename: str, data: list[dict]):
    path = Path(filename)
    if path.exists():
        with open(path, mode='a', newline='', encoding='utf-8') as file:
            # 创建一个csv.DictWriter对象，指定字段名
            writer = csv.DictWriter(file, fieldnames=data[0].keys())

            # 写入数据行
            for row in data:
                writer.writerow(row)
    else:
        with open(path, mode='a', newline='', encoding='utf-8') as file:
            # 创建一个csv.DictWriter对象，指定字段名
            writer = csv.DictWriter(file, fieldnames=data[0].keys())

            # 写入标题行
            writer.writeheader()

            # 写入数据行
            for row in data:
                writer.writerow(row)


# uuid
def _uid_key() -> str:
    _uid = uuid.uuid4()
    return _uid.__str__()


# 获取两个时间对象之间的月份列表[(2024, 11)],日期对象写反返回空列表
def _get_month_range_bak(start: datetime | date, end: datetime | date) -> list[tuple]:
    """
    :param start:
    :param end:
    :return: [(2024, 11),,,]
    """
    print(start, end)
    _months = (end.year - start.year) * 12 + end.month - start.month  # 求月份差

    if start.day <= 28:
        _start = start.month - 1  # 开始月不计入
    else:
        _start = start.month

    # if end.day <= 28:
    #     _end = end.month
    # else:
    #     _end = end.month-1

    _end = end.month

    print(_start, _end)

    _range = [(start.year + mon // 12, mon % 12 + 1) for mon in range(_start, _start + _end)]

    return _range


# 人民币金额转大写
def _rmb_upper(value) -> str:
    """
    人民币大写
    传入浮点类型的值返回 unicode 字符串
    """
    map_ = [u"零", u"壹", u"贰", u"叁", u"肆", u"伍", u"陆", u"柒", u"捌", u"玖"]
    unit = [u"分", u"角", u"元", u"拾", u"百", u"千", u"万", u"拾", u"百", u"千", u"亿",
            u"拾", u"百", u"千", u"万", u"拾", u"百", u"千", u"兆"]

    nums = []  # 取出每一位数字，整数用字符方式转换避大数出现误差
    for i in range(len(unit) - 3, -3, -1):
        if value >= 10 ** i or i < 1:
            nums.append(int(round(value / (10 ** i), 2)) % 10)

    words = []
    zflag = 0  # 标记连续0次数，以删除万字，或适时插入零字
    start = len(nums) - 3
    for i in range(start, -3, -1):  # 使i对应实际位数，负数为角分
        if 0 != nums[start - i] or len(words) == 0:
            if zflag:
                words.append(map_[0])
                zflag = 0
            words.append(map_[nums[start - i]])
            words.append(unit[i + 2])
        elif 0 == i or (0 == i % 4 and zflag < 3):  # 控制‘万/元’
            words.append(unit[i + 2])
            zflag = 0
        else:
            zflag += 1

    if words[-1] != unit[0]:  # 结尾非‘分’ 补 '整'字
        words.append(u"整")
    return ''.join(words)


# 两个日期之间的月数
def _month_diff(dt1, dt2) -> int:
    diff_ = relativedelta.relativedelta(dt2, dt1)
    y_ = diff_.years
    m_ = diff_.months
    d_ = diff_.days

    res = y_ * 12 + m_

    return res if d_ < 15 else res + 1


# 获取两个时间对象之间的月份列表[(2024, 11)],日期对象写反返回空列表
def _get_month_range(start: datetime | date, end: datetime | date) -> list[tuple]:
    """
    :param start:
    :param end:
    :return: [(2024, 11),,,]
    """
    _months = (end.year - start.year) * 12 + end.month - start.month  # 求月份差
    _range = [(start.year + mon // 12, mon % 12 + 1) for mon in range(start.month - 1, start.month + _months)]

    if start.day >= 28:
        _range.pop(0)
    if end.day <= 10:
        _range.pop(-1)

    return _range


class PointRecords:
    """
        有害垃圾清运记录
        1.通过点位查询对应的点位信息
        2.点位信息求 _id 对应查询 记录中 fk=_id 的清运记录
        3.随机生成清运记录 对应的时间对象之间（每月1次）
        4.清运记录有真有假混合生成
        5.输出 给数据库，csv json  给模板文件生成word

    """

    def __init__(self, start: datetime, end: datetime):
        self.start = start
        self.end = end

        self.point_json = {}
        self.records = []
        self.summary = []

    # 从真实的记录中获取年月的列表
    def __find_year_month_from_really_records(self) -> list[tuple]:
        return [(r['datetime'].year, r['datetime'].month) for r in self.records]

    # 起始时间与终止时间中间月份列表
    def __date_range(self) -> list[tuple]:
        return _get_month_range(self.start, self.end)

    @property
    def total(self) -> float | int:
        if self.summary:
            t = sum([s['w_total'] for s in self.summary])
            return t
        else:
            return 0

    # 封装获取点位字典的方法
    def get_point_data(self, fn, name=None):
        if fn:
            self.point_json = fn(name)  # 从数据查询函数返回dict

        else:
            self.point_json = {
                '_id': 'p-1001',
                'custom': '旺泉街道',
                'area_name': '悦君家园',
                'point_name': '悦君家园小区西门',
                'address': '北京市顺义区西外街19号靠近当代北辰悦MOMA',
                'point_no': '1#',
                'long': 116.6413128,
                'lat': 40.1439299,
                'unit': '中海悦君家园管理处',
                'manager': '王海朋',
                'tel': '13387346013',
                'notice': '小区西门，车不好进',
                'point_img': ['pic1......', 'pic2.....'],
            }
        return self

    # 封装获取日期范围记录的方法
    def really_records(self, fn):
        if fn:
            fk = self.point_json.get('_id')  # 获取fk
            start, end = _date_to_datetime(self.start), _date_to_datetime(self.end)  # 日期对象强制转为datetime
            self.records = fn(fk, start, end)  # 数据查询函数 返回list

            for _idx, _one in enumerate(self.records, start=1):
                _one['date'] = datetime.strftime(_one['datetime'], '%Y/%m/%d')  # 增加日期
                _one['time'] = datetime.strftime(_one['datetime'], '%H:%M')  # 增加时间
                _one['is_really'] = 1  # 真
                _sum = {
                    'idx': _idx,
                    'datetime': _one.get('datetime'),
                    'date': _one.get('date'),
                    'time': _one.get('time'),
                    'w_total': _one.get('w_total'),
                    'is_really': 1
                }
                self.summary.append(_sum)

        return self

    @staticmethod  # 随机时间对象
    def __rnd_datetime(year, month):
        _d = random.randint(10, 27)
        _hh = random.randint(6, 22)
        _mm = random.randint(0, 59)
        return datetime(year, month, _d, _hh, _mm, 0)

    # 单月随机记录
    def rnd_one_month_record(self, year, month) -> dict:
        if self.point_json:
            fk = self.point_json.get('_id')
        else:
            fk = 'tst'
        # 处理datetime 对象
        __dt = self.__rnd_datetime(year, month)  # datetime.datetime(2024, 1, 1, 17, 21)
        __date: str = __dt.strftime('%Y/%m/%d')
        __time: str = __dt.strftime('%H:%M')
        __weight: dict = {f"w{i}": random.randint(10, 20) for i in range(1, 9)}
        __w_total = sum(__weight.values())

        # 随机重量记录
        __rec = {
            '_id': _uid_key(),
            'fk': fk,
            'datetime': __dt,
            'date': __date,
            'time': __time,
            'f_pic': ['图片1', '图片2'],
            'b_pic': ['图片1', '图片2'],
            'w_total': __w_total,
            'is_really': 0
        }
        __rec.update(__weight)

        return __rec

    # 随机记录 月范围
    def rnd_records(self):
        # 增加处理 真实记录中存在相同月份的数据（遇到相同月份的真实数据，跳过或追加）

        _idx = 1
        _range: list[tuple] = self.__date_range()

        if _range:
            for year, month in _range:
                _one = self.rnd_one_month_record(year, month)
                _sum = {
                    'idx': _idx,  # 增加处理具有真实数据的情况（放在一起按时间顺序排列）
                    'datetime': _one.get('datetime'),
                    'date': _one.get('date'),
                    'time': _one.get('time'),
                    'w_total': _one.get('w_total'),
                    'is_really': 0
                }

                self.records.append(_one)
                self.summary.append(_sum)

                _idx += 1

            return self
        else:
            raise "日期写反了"

    # 随机记录 月范围
    def rnd_records_2(self):
        # 增加处理 真实记录中存在相同月份的数据（遇到相同月份的真实数据，跳过或追加）
        if not self.records:

            return self.rnd_records()

        else:

            _lst = self.__find_year_month_from_really_records()

            _idx = 1
            _range: list[tuple] = self.__date_range()

            if _range:
                _filter = filter(lambda x: x not in _lst, _range)
                _ = list(_filter)

                for year, month in _:
                    _one = self.rnd_one_month_record(year, month)
                    _sum = {
                        'idx': _idx,  # 增加处理具有真实数据的情况（放在一起按时间顺序排列）
                        'datetime': _one.get('datetime'),
                        'date': _one.get('date'),
                        'time': _one.get('time'),
                        'w_total': _one.get('w_total'),
                        'is_really': 0
                    }

                    self.records.append(_one)
                    self.summary.append(_sum)

                    _idx += 1

                # 数据按时间排序
                self.records = sorted(self.records, key=lambda r: datetime.strftime(r['datetime'], '%Y-%m-%d %H:%M'))
                self.summary = sorted(self.summary, key=lambda r: datetime.strftime(r['datetime'], '%Y-%m-%d %H:%M'))

                for idx, item in enumerate(self.summary, 1):
                    item['idx'] = idx

                return self
            else:
                raise "日期写反了"

    # 数据输出
    def to_word_tpl(self):
        self.point_json.update({
            'records': self.records,
            'summary': self.summary,
            'total': self.total
        })
        return self.point_json

    def to_json_file(self):
        # 把record里的datetime对象去掉，保持可序列化的数据
        __rec = self.to_jsondb()
        self.point_json.update({
            'records': __rec,
            'summary': self.summary,
            'total': self.total
        })

        return self.point_json

    def to_jsondb(self):
        __rec = self.records
        for __r in __rec:
            __r.pop('datetime')
        return __rec

    def to_mongodb(self):
        __rec = self.records

        for __r in __rec:
            __r.pop('date')
            __r.pop('time')

        return __rec


# 生成有害垃圾点位记录
def point_record_docx(start: datetime | date, end: datetime | date, fn1=None, fn2=None, point_name=None) -> dict:
    """
        记录数据
    :param fn2: 获取真实记录的方法
    :param fn1: 获取point 数据的方法
    :param point_name:点位名称
    :param end:
    :param start:
    :return: dict

    """

    _p = (
        PointRecords(start, end)
            .get_point_data(fn1, point_name)  # 获取point 数据
            .really_records(fn2)  # 获取真实记录
            .rnd_records_2()  # 随机记录
    )

    # _dict_to_csv('../tmp/清运记录.csv', _p.to_mongodb())

    return _p.to_word_tpl()


def three_price_docx(json_=None) -> dict:
    if json_ is None:
        json_ = {
            'partA': '北京市顺义区马坡第一幼儿园',
            'start_date': '2024/11/1',
            'end_date': '2025/7/31',
            'three': [
                {
                    "name": "北京宏鑫汇能循环经济科技发展有限公司",
                    'address': '北京市顺义区南法信镇南卷村东200米',
                    'user': '刘鹏',
                    'tel': '010-69471928',
                    "signal_price": 500.00,
                },
                {
                    "name": "北京智分瓦力环境工程有限公司",
                    'address': '北京市顺义区后沙峪镇安福街6号1362室',
                    'user': '刘平',
                    'tel': '18511982023',
                    "signal_price": 580.00,
                },
                {
                    "name": "北京铨胜恒德再生资源有限公司",
                    'address': '北京市顺义区南彩镇俸伯村西（村委会西500米）',
                    'user': '王鹏辉',
                    'tel': '15010190271',
                    "signal_price": 650.00,
                },
            ]
        }

    start = datetime.strptime(json_['start_date'], "%Y/%m/%d")
    end = datetime.strptime(json_['end_date'], "%Y/%m/%d")

    service_date = f"{start.year}年{start.month}月{start.day}日至{end.year}年{end.month}月{end.day}日"
    months = _month_diff(start, end)  # 月差
    nums = 1
    json_.update({'service_date': service_date, 'months': months})
    for itm in json_['three']:
        price = itm['signal_price'] * months * nums
        itm.update({
            'service_date': service_date,
            'months': f"{months}",
            'nums': f"{nums}",
            'signal_price': f"{itm['signal_price']:.2f}",
            'price': f"{price:.2f}",
            'up_price': _rmb_upper(price),
            'sign_date': '2024/10/8'
        })

    return json_


# 生成合同需要的数据
def contract_docx(json_=None) -> dict:
    if json_ is None:
        json_ = {
            'partA': {
                'name': '北京市顺义区马坡第一幼儿园',
                'tax': '',
                'bank': '',
                'account': '',
                'tel': '',
                'address': '',
            },
            'partB': {
                'name': '北京宏鑫汇能循环经济科技发展有限公司',
                'tax': '911101131025153202',
                'bank': '北京农商银行顺义支行',
                'account': '0801000103000050438',
                'tel': '',
                'address': '北京市顺义区南法信镇南卷村东200米'
            },
            'service_local': '北京市顺义区马坡第一幼儿园',
            'start_date': '2024/11/1',
            'end_date': '2025/7/31',
            'signal_price': 500.00,
        }

    start = datetime.strptime(json_['start_date'], "%Y/%m/%d")
    end = datetime.strptime(json_['end_date'], "%Y/%m/%d")

    service_date = f"自【 {start.year} 】年【 {start.month} 】月【 {start.day} 】日起至【 {end.year} 】年【 {end.month} 】月【 {end.day} 】止"
    months = _month_diff(start, end)  # 月差
    price = json_['signal_price'] * months
    up_price = _rmb_upper(price)

    json_.update({
        'service_date': service_date,
        'signal_price': f"{json_['signal_price']:.2f}",
        'months': f"{months}",
        'price': f"{price:.2f}",
        'up_price': up_price
    })

    return json_


# 模拟生成数据csv
def _records_tst():
    _comps = [
        {
            "_id": "c-0006",
            "ABC": "顺商集团",
            "name": "北京顺义商业集团有限公司",
            "jur_person": "李浩",
            "address": "北京市顺义区仁和地区府前西街1号1幢6层",
            "tax": "91110113MA00DQ6D3C",
            "bank": "北京银行顺义支行",
            "account": "20000034428100016343054",
            "tel_1": "010-81487888",
            "relation": "有害垃圾-清运",
            "user": "孟远",
            "tel_2": "nan"
        },
        {
            "_id": "c-0007",
            "ABC": "旺泉街道",
            "name": "北京市顺义区旺泉街道办事处",
            "jur_person": '',
            "address": "北京市顺义区望泉北街1号",
            "tax": "1111011066841756XE",
            "bank": "工行北京中山街支行",
            "account": "0200041209200065590",
            "tel_1": "010-61409508",
            "relation": "有害垃圾-清运",
            "user": "马壮",
            "tel_2": "nan"
        },
    ]

    with open('../db/有害垃圾点位.json', mode='r', encoding='utf-8') as f:
        points = json.load(f)

    points = list(filter(lambda x: x['point'] is not None, points))
    sample = random.sample(points, 35)  # 随机选取点位名称35个
    for point in sample:
        dt1 = datetime.strptime('2024-11-1', "%Y-%m-%d")
        dt2 = datetime.strptime(f'2025-{random.randint(1, 10)}-28', "%Y-%m-%d")

        p = PointRecords(dt1, dt2)
        p.point_json = point  # point 数据
        p.rnd_records()  # 随机记录

        _dict_to_csv('../tmp/清运记录.csv', p.to_mongodb())  # 存储到文件中


# 模板生成文件
def word_file(tpl, context, filename):
    docx = DocxTemplate(template_file=tpl)
    docx.render(context=context)
    docx.save(filename=filename)
    print('完成...')


def run_three_price():
    tpl = r'..\tpl\模版_三方比价_有害垃圾清运.docx'
    context = three_price_docx()
    filename = f"res/三方比价_有害垃圾_{context.get('partA')}.docx"
    word_file(tpl, context, filename)


def run_contract():
    tpl = r'..\tpl\模版_服务合同_有害垃圾委托清运.docx'
    context: dict = contract_docx()
    filename = f"res/服务合同_有害垃圾_{context['partA'].get('name')}.docx"
    word_file(tpl, context, filename)


def run_clear_records():

    def _fn1(name: str):
        _df = pd.read_json('../db/有害垃圾点位.json')
        _df.to_dict()
        _df_one = _df[_df['point'] == name]

        return _df_one.to_dict(orient="records")[0]

    def _fn2(fk, start, end):
        _df = pd.read_csv('../db/清运记录.csv')
        _df = _df[_df['fk'] == fk]
        _df['datetime'] = pd.to_datetime(_df['datetime'], format='%Y-%m-%d %H:%M:%S')
        _df = _df[(_df['datetime'] >= start) & (_df['datetime'] <= end)]
        return _df.to_dict(orient='records')

    dt1 = datetime.strptime('2024-11-1', "%Y-%m-%d")
    dt2 = datetime.strptime('2025-10-30', "%Y-%m-%d")

    tpl = r'..\tpl\模板_记录_有害垃圾清运.docx'
    context = point_record_docx(fn1=_fn1, fn2=_fn2, start=dt1, end=dt2, point_name='顺商国泰大厦店')
    filename = f"res/记录_有害垃圾清运_{context.get('_id')}.docx"

    word_file(tpl, context, filename)


if __name__ == '__main__':
    run_three_price()
    # run_contract()
    # run_clear_records()
    #
    # _records_tst()

    pass
